Imports pixolut.BizBlox
Imports System.Reflection
Imports System.io
Imports System.Text

Public MustInherit Class AbstractRenderEngine

    Private tables As TableList
    Private updateHnd As UpdateProgress
    Private statusHnd As UpdateStatus
    Private fileParserType As Type

    Public Delegate Sub UpdateProgress(ByVal current As Integer, ByVal max As Integer)
    Public Delegate Sub UpdateStatus(ByVal str As String)

    'Protected MustOverride Function RenderCore(ByVal t As Table) As String
    Protected MustOverride Function RenderPrefix(ByVal t As Table) As String
    Protected MustOverride Function RenderPostfix(ByVal t As Table) As String

    'New Stuff
    Protected MustOverride Function RenderElements(ByVal t As Table) As String
    Protected MustOverride Function RenderRegionStart() As String
    Protected MustOverride Function RenderRegionEnd() As String
    Protected MustOverride Function RenderColumn(ByVal t As Table, ByVal index As Integer) As String
    Protected MustOverride Function RenderForeignColumn(ByVal t As Table, ByVal index As Integer) As String
    Protected MustOverride Function FormatFilename(ByVal tableName As String) As String
    Protected MustOverride Function FormatFiletype() As String
    Protected MustOverride Function FormatComment() As String
    Protected MustOverride Function RenderAttributes(ByVal attributes() As Attribute) As String

    Private Const X_ELEMENTS As String = "+Elements"
    Private Const X_FOREIGN As String = ".ForeignReference"


    Public Sub New(ByVal u As UpdateProgress, ByVal s As UpdateStatus, ByVal fileParser As Type)
        updateHnd = u
        statusHnd = s
        fileParserType = fileParser
    End Sub

    Private m_project As BBXProject
    Public Property Project() As BBXProject
        Get
            Return m_project
        End Get
        Set(ByVal value As BBXProject)
            m_project = value
        End Set
    End Property

    Public Sub Import(ByVal InputFilename As String)
        Dim src As String = ReadFromDisk(InputFilename)
        Dim obj As AbstractFileParser = CType(fileParserType.InvokeMember(Nothing, BindingFlags.DeclaredOnly Or BindingFlags.Public Or BindingFlags.NonPublic Or BindingFlags.Instance Or BindingFlags.CreateInstance, Nothing, Nothing, New Object() {}), AbstractFileParser)
        tables = obj.Parse(src, Me.Project)
    End Sub

    Public Sub OutputClasses(ByVal OutputPath As String, ByVal tables As TableList)
        For i As Integer = 0 To tables.Count - 1
            Output(tables(i), OutputPath & "\" & FormatFilename(tables(i).TableName) & FormatFiletype())
            updateHnd((i + 1), tables.Count)
        Next
    End Sub

    Private Sub FileToDisk(ByVal src As String, ByVal filePath As String)
        'Try
        Dim out As StreamWriter
        out = File.CreateText(filePath)
        out.Write(src.Trim())
        out.Flush()
        out.Close()
        'Catch ex As Exception
        '    MsgBox("Fatal error writing to disk." & vbCrLf & ex.Message, MsgBoxStyle.Critical Or MsgBoxStyle.OKOnly, "BizBlox Error")
        '    End
        'End Try
    End Sub

    Private Function ReadFromDisk(ByVal filename As String) As String
        'Try
        Dim src As StreamReader
        Dim inData As String
        src = File.OpenText(filename)
        inData = src.ReadToEnd()
        src.Close()
        Return inData.Trim()
        'Catch ex As Exception
        '    MsgBox("Fatal error reading from disk." & vbCrLf & ex.Message, MsgBoxStyle.Critical Or MsgBoxStyle.OKOnly, "BizBlox Error")
        '    End
        'End Try
    End Function

    Private Sub Output(ByVal t As Table, ByVal filename As String)
        If File.Exists(filename) Then
            Dim existing As OutputFile = Me.GetExistingFile(filename)
            Try
                Dim mc As New MarkerCollection(existing.FullText, AddressOf Me.FormatComment)
                Dim newCore As String = RenderCore(t, mc)
                Dim updated As Boolean = True
                Dim newEntity As String = InjectEntityIntoExistingFile(existing, newCore, updated)
                If updated Then
                    statusHnd("Injecting " & Path.GetFileName(filename))
                    FileToDisk(newEntity, filename)
                Else
                    statusHnd("No Update: " & Path.GetFileName(filename))
                End If
            Catch ex As Exception
                Debug.Print(ex.ToString())
                statusHnd("Failed updating " & Path.GetFileName(filename) & " (" & ex.Message & ")")
            End Try
        Else
            statusHnd("Creating " & Path.GetFileName(filename))
            Try
                Dim mc As New MarkerCollection(AddressOf Me.FormatComment)
                Dim core As String = Me.RenderCore(t, mc)
                FileToDisk(RenderEntityInNewFile(t, core), filename)
            Catch ex As Exception
                Debug.Print(ex.ToString())
                statusHnd("Failed creating " & Path.GetFileName(filename) & " (" & ex.Message & ")")
            End Try
        End If
    End Sub


    Private Function GetExistingFile(ByVal filename As String) As OutputFile
        Dim existingSource As String = ReadFromDisk(filename)
        Dim f As New OutputFile()
        f.FullText = existingSource
        Dim lines() As String = existingSource.Split(vbLf)
        Dim sb As New StringBuilder
        Dim coreStarted As Boolean = False
        Dim coreFinished As Boolean = False
        For i As Integer = 0 To lines.Length - 1
            'updateHnd(i, l.Length - 1)
            Dim l As String = lines(i).TrimEnd(vbCr, vbLf, vbCrLf) & vbCrLf
            If l.IndexOf(Constants.START_TAG) > -1 Then
                'Do nothing
            ElseIf l.IndexOf(Constants.LASTUPDATED_TAG) > -1 Then
                coreStarted = True
            ElseIf l.IndexOf(Constants.END_TAG) > -1 Then
                coreFinished = True
            Else
                If Not coreStarted Then
                    f.Top &= l
                ElseIf coreFinished Then
                    f.Bottom &= l
                Else
                    f.Core &= l
                End If
            End If
        Next
        Return f
    End Function

    Private Function InjectEntityIntoExistingFile(ByVal existingFile As OutputFile, ByVal newCore As String, ByRef updated As Boolean) As String
        Dim sb As New StringBuilder
        'Only perform the injection if the core has changed from the file version.
        'This saves updating every file if only one has changed, so reduces need to
        'commit new versions in source control
        If newCore = existingFile.Core Then
            updated = False
            Return ""
        Else
            updated = True
            sb = sb.Append(existingFile.Top())
            sb = sb.Append(FormatComment()).Append(Constants.START_TAG).Append(vbCrLf)
            sb = sb.Append(Me.GetLastUpdatedString())
            sb = sb.Append(newCore)
            sb = sb.Append(FormatComment()).Append(Constants.END_TAG).Append(vbCrLf)
            sb = sb.Append(existingFile.Bottom())
        End If
        Return sb.ToString()
    End Function

    Private Function RenderEntityInNewFile(ByVal t As Table, ByVal src As String) As String
        Dim sb As New StringBuilder
        sb = sb.Append(RenderPrefix(t)).Append(vbCrLf)
        sb = sb.Append(FormatComment()).Append(Constants.START_TAG).Append(vbCrLf)
        sb = sb.Append(Me.GetLastUpdatedString())
        sb = sb.Append(src)
        sb = sb.Append(FormatComment()).Append(Constants.END_TAG).Append(vbCrLf)
        sb = sb.Append(RenderPostfix(t)).Append(vbCrLf)
        Return sb.ToString()
    End Function

    Protected Function GetLastUpdatedString() As String
        Dim sb As New StringBuilder
        sb.Append(FormatComment()).Append(Constants.LASTUPDATED_TAG).Append(Now.ToString("F")).Append(vbCrLf)
        Return sb.ToString()
    End Function

    Protected Function GetVersionString(ByVal sb As StringBuilder) As StringBuilder
        sb = sb.Append(FormatComment()).Append("BizBlox Version : ").Append((New DataAccess).GetType().Assembly.GetName.Version.ToString()).Append(vbCrLf)
        Return sb
    End Function

    Private Function RenderCore(ByVal t As Table, ByVal mc As MarkerCollection) As String
        Dim m As Marker
        Dim sb As New StringBuilder
        sb = Me.GetVersionString(sb)
        sb = sb.Append(RenderRegionStart()).Append(vbCrLf)
        m = mc.GetMarker(X_ELEMENTS)
        If m Is Nothing Then m = New Marker(X_ELEMENTS)
        If Not m.DoNotModify Then m.Update(Me.RenderElements(t), AddressOf Me.FormatComment)
        sb = sb.Append(m.ToString())

        For j As Integer = 0 To t.ColumnCount - 1
            m = mc.GetMarker(t(j).ColumnName)
            If m Is Nothing Then m = New Marker(t(j).ColumnName)
            If Not m.DoNotModify Then
                m.Update(Me.RenderColumn(t, j), AddressOf Me.FormatComment)
            End If

            sb = sb.Append(m.ToString())

            If t(j).FKColumn = True Then
                m = mc.GetMarker(t(j).ColumnName & X_FOREIGN)
                If m Is Nothing Then m = New Marker(t(j).ColumnName & X_FOREIGN)
                If Not m.DoNotModify Then m.Update(Me.RenderForeignColumn(t, j), AddressOf Me.FormatComment)
                sb = sb.Append(m.ToString())
            End If
        Next
        sb = sb.Append(RenderRegionEnd()).Append(vbCrLf)
        Return sb.ToString()
    End Function
End Class
